// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Text;
using System.Collections;
using System.Collections.Specialized;

namespace Microsoft.Tools.CodeRunner
{
	/// <summary>
	/// Flags for TaggedString operations
	/// </summary>
	[Flags]
	public enum TaggedStringOptions
	{
		/// <summary>
		/// Search case sensitively and leave unknown tags in the output
		/// </summary>
		Default = 0x00,
		/// <summary>
		/// Do a case sensitive search for tags
		/// </summary>
		CaseSensitive = 0x01,
		/// <summary>
		/// Tags that are not found in the dictionary are replaced with an empty string
		/// instead of being left in the output.
		/// </summary>
		RemoveUnknownTags = 0x02
	}
	
	/// <summary>
	/// Summary description for TaggedString.
	/// </summary>
	public sealed class TaggedString
	{
		private string tagPrefix = "%";
		
		/// <summary>
		/// Description of property TagPrefix.
		/// </summary>
		public string TagPrefix
		{
		    get { return tagPrefix; }
		    set { tagPrefix = value; }
		}

		private string tagSuffix = "%";
		
		/// <summary>
		/// Description of property TagSuffix.
		/// </summary>
		public string TagSuffix
		{
		    get { return tagSuffix; }
		    set	{ tagSuffix = value; }
		}
				
	
		/// <overloads>Constructs a <see cref="TaggedString"/> instance</overloads>
		/// <summary>
		/// Constructs a new instance of <see cref="TaggedString"/> using the '%' character as the tag prefix and suffix
		/// </summary>
		public TaggedString() {}
		
		/// <summary>
		/// Constructs a <see cref="TaggedString"/> using the supplied tag delimiters
		/// </summary>
		/// <param name="tagPrefix"></param>
		/// <param name="tagSuffix"></param>
		public TaggedString(string tagPrefix, string tagSuffix)
		{
			this.tagPrefix = tagPrefix;
			this.tagSuffix = tagSuffix;
		}

		/// <summary>
		/// This method searches for each occurrence of a tagged variable in <c>source</c> and 
		/// replaces it with the value from the a dictionary <c>subs</c>.  Comparisons are done case
		/// insensitively. 
		/// </summary>
		/// <param name="source">String containing tagged entities</param>
		/// <param name="dict">A dictionary of tag values</param>
		/// <returns>A string with all tags replaced</returns>
		public string Replace(string source, IDictionary dict)
		{
			return Replace(source, dict, TaggedStringOptions.Default);
		}
		
		/// <summary>
		/// This method searches for each occurrence of a tagged variable in <c>source</c> and 
		/// replaces it with the value from the a dictionary <c>subs</c>.  Comparisons are done case
		/// insensitively. 
		/// </summary>
		/// <param name="source">String containing tagged entities</param>
		/// <param name="dict">A dictionary of tag values</param>
		/// <param name="flags"><see cref="TaggedStringOptions"/> for the replace operation</param>
		/// <returns>A string with all tags replaced</returns>
		public string Replace(string source, IDictionary dict, TaggedStringOptions flags)
		{	
			StringBuilder sb = new StringBuilder(source);
			ITagFinder finder = (flags & TaggedStringOptions.CaseSensitive) != 0 ? 
				(ITagFinder)caseSensitiveTageFinder : (ITagFinder)caseInsensitiveTageFinder;

			foreach (DictionaryEntry e in dict)
			{
				int i = 0;
				int j = 0;
				string pattern = tagPrefix + ((string)e.Key) + tagSuffix;

				while ((j = finder.Find(sb, pattern, i)) != -1)
				{
					sb.Remove(j, pattern.Length);
					sb.Insert(j, e.Value);
					i = j + ((string)e.Value).Length;
				}
			}
			
			if ((flags & TaggedStringOptions.RemoveUnknownTags) != 0)
			{
				int i = 0;
				
				while ((i = caseSensitiveTageFinder.Find(sb, tagPrefix, i)) != -1)
				{
					int j = caseSensitiveTageFinder.Find(sb, tagSuffix, i + tagPrefix.Length);
					
					if (j == -1)
						// If we find an unclosed tag, we are done
						break;

					sb.Remove(i, j + tagSuffix.Length - i);
				}
			}
			
			return sb.ToString();
		}

		interface ITagFinder
		{
			int Find(StringBuilder sb, string pattern, int start);
		}
	
		class CaseSensitiveTagFinder : ITagFinder
		{
			public int Find(StringBuilder sb, string pattern, int start)
			{
				for (int i = start; i <= sb.Length - pattern.Length; i++)
				{
					int j;
				
					for (j = 0; j < pattern.Length; j++)
						if (sb[i + j] != pattern[j])
							break;
						
					if (j == pattern.Length)
						return i;
				}
			
				return -1;
			}
		}
	
		class CaseInsensitiveTagFinder : ITagFinder
		{
			public int Find(StringBuilder sb, string pattern, int start)
			{
				for (int i = start; i <= sb.Length - pattern.Length; i++)
				{
					int j;
				
					for (j = 0; j < pattern.Length; j++)
						if (Char.ToLower(sb[i + j]) != Char.ToLower(pattern[j]))
							break;
						
					if (j == pattern.Length)
						return i;
				}
			
				return -1;
			}
		}
		
		private static CaseInsensitiveTagFinder caseInsensitiveTageFinder = new CaseInsensitiveTagFinder();
		private static CaseSensitiveTagFinder caseSensitiveTageFinder = new CaseSensitiveTagFinder();
	}
}
